3 * RGB(W) LED IM Dendrite module (for ESP8266)
4 * Created for Interplaymediumâ„¢ project (https://interplaymedium.org)
5 * Copyright © 2016 Dmitry Shalnov [interplaymedium.org]
6 * Licensed under the Apache License, Version 2.0
11 #include <ESP8266WiFi.h>
12 #include <WiFiClient.h>
13 #include <ESP8266WebServer.h>
14 #include <ESP8266mDNS.h>
17 #define VERSION "0.1.4"
19 #define EEPROM_STR_MAX_LEN 16
20 #define EEPROM_OTHER_MAX_LEN 8
23 #define EEPROM_STR_FLAG 17 // watch overlap: EEPROM_STR_MAX_LEN
28 #define EEPROM_ROT_H 22
29 #define EEPROM_ROT_L 23
31 // -------------------- PWM settings ----------------------------------------------------
34 #include "pwm.h" // Includes of Expressif SDK
42 #define PWM_CHANNELS 4
45 #define PWM_PERIOD 5000 // Period of PWM frequency, SDK default: 5000 -> * 200ns ^= 1 kHz
47 unsigned int CIEL8[ PWMSTEPS ];
49 int R=0, G=0, B=0, W=0;
50 unsigned int Rnew=0, Gnew=0, Bnew=0, Wnew=0;
51 unsigned int rotateDelay = 0;
52 int signR = 1, signG = 1, signB = 1, signW = 1;
53 unsigned int eTimer = 0;
55 uint32 pwm_duty_init[PWM_CHANNELS]; // PWM initial duty: OFF by default
57 uint32 io_info[PWM_CHANNELS][3] = {
61 // {PERIPHS_IO_MUX_GPIO5_U, FUNC_GPIO5, 5}, // D1
62 // {PERIPHS_IO_MUX_GPIO4_U, FUNC_GPIO4, 4}, // D2
63 {PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0, LED1}, // D3 0
64 {PERIPHS_IO_MUX_GPIO2_U, FUNC_GPIO2, LED2}, // D4 2
66 {PERIPHS_IO_MUX_U0RXD_U, FUNC_GPIO3, LED3}, // RX 3
67 {PERIPHS_IO_MUX_U0TXD_U, FUNC_GPIO1, LED4}, // TX 1
69 // {PERIPHS_IO_MUX_MTMS_U, FUNC_GPIO14, 14}, // D5
70 // {PERIPHS_IO_MUX_MTDI_U, FUNC_GPIO12, 12}, // D6
71 // {PERIPHS_IO_MUX_MTCK_U, FUNC_GPIO13, 13}, // D7
72 // {PERIPHS_IO_MUX_MTDO_U, FUNC_GPIO15 ,15}, // D8
74 // D0 - not have PWM :-( 16
77 // ------------------- server settings --------------------------------------------------
79 #define HOST_DEFAULT "im_rgb5"
81 char host[ EEPROM_STR_MAX_LEN ];
83 const char* ssid = IM_WIFI_SSID;
84 const char* password = IM_WIFI_PASS;
86 ESP8266WebServer server(80);
90 // ----------------- change current R G B (W) to another RGBW set ----------------------
92 void changeRGBto( int newR, int newG, int newB, int newW ){
94 float stepR, stepG, stepB, stepW;
99 // R = EEPROM.read( 0 );
100 // G = EEPROM.read( 1 );
101 // B = EEPROM.read( 2 );
102 // W = EEPROM.read( 3 );
104 stepR = (newR - R) / (float)PWMSTEPS;
105 stepG = (newG - G) / (float)PWMSTEPS;
106 stepB = (newB - B) / (float)PWMSTEPS;
107 stepW = (newW - W) / (float)PWMSTEPS;
109 for ( unsigned int a = 0; a < PWMSTEPS; a++ ){
111 R2 = R + round( stepR * (float)a );
112 G2 = G + round( stepG * (float)a );
113 B2 = B + round( stepB * (float)a );
114 W2 = W + round( stepW * (float)a );
116 pwm_set_duty( CIEL8[R2], 0 );
117 pwm_set_duty( CIEL8[G2], 1 );
118 pwm_set_duty( CIEL8[B2], 2 );
119 pwm_set_duty( CIEL8[W2], 3 );
121 pwm_start(); // commit
123 delay( 500 / PWMSTEPS );
127 // ----------- explode for selected substring -----------
129 String expld( String str, unsigned int numb, char delimiter ){
131 unsigned int cnt = 0, a = 0, p2 = 0, p1 = 0, lng = 0;
135 for ( a = 0; a < lng; a++ ){
136 if ( str.charAt( a ) == delimiter ) {
139 if ( cnt == numb ) break;
149 if ( numb > 0 ) p2 ++;
151 return str.substring(p2, p1);
155 unsigned char URIHasArg( String str, String arg ){
158 unsigned char a = 0, delimiterCnt = 0, lng = str.length();
160 for ( a = 0; a < lng; a++ ){
161 if ( str.charAt( a ) == '/' ) delimiterCnt++;
164 for ( a=0; a < 10; a++ ){
165 if ( arg.equals( expld( str, a, '/' ) ) ) return a;
170 // ---------------- EEPROM String r/w -------------------
172 void EEPROMStrRead( unsigned char addr, char * str ){
173 for (unsigned char a = addr; a < EEPROM_STR_MAX_LEN; a++ ){
174 str[ a ] = EEPROM.read( a );
175 if (str[ a ] == 0) break;
179 void EEPROMStrWrite( unsigned char addr, char * str ){
181 for (a = addr; a < EEPROM_STR_MAX_LEN; a++ ){
182 EEPROM.write( a, str[ a ] );
183 if (str[ a ] == 0) break;
185 EEPROM.write( a, 0 );
188 // ---------------- misc ---------------------------------
190 int str2HEX(const String str) {
191 return strtol( str.c_str(), 0, 16 );
194 void blink( unsigned char times ){
195 for (unsigned char a = 0; a < times; a++ ){
196 digitalWrite(LED4, HIGH);
198 digitalWrite(LED4, LOW);
203 // --------------- Init --------------------------------
207 HTTPresp.reserve(800); // lenght of Help message generally
209 // calculate lookup array
211 for (unsigned int a = 1; a < PWMSTEPS; a++) CIEL8[ a ] = round( pow( PWM_PERIOD, (double)a / (PWMSTEPS-1) ) ); // calculate exponential lookup array for PWM
216 pinMode(LED1, OUTPUT);
217 pinMode(LED2, OUTPUT);
218 pinMode(LED3, OUTPUT);
219 pinMode(LED4, OUTPUT);
221 digitalWrite(LED1, LOW);
222 digitalWrite(LED2, LOW);
223 digitalWrite(LED3, LOW);
224 digitalWrite(LED4, LOW);
228 for (uint8_t channel = 0; channel < PWM_CHANNELS; channel++) pwm_duty_init[channel] = 0; // Initial duty -> all off
229 uint32_t period = PWM_PERIOD;
230 pwm_init(period, pwm_duty_init, PWM_CHANNELS, io_info);
235 Serial.begin(115200);
237 Serial.println("Booting Sketch...0");
242 // deviceURI.reserve(7);
243 // deviceURI = "im" + WiFi.macAddress().substring(12, 14) + WiFi.macAddress().substring(15, 17); // 00:00:00:00:00:00
245 EEPROM.begin( EEPROM_STR_MAX_LEN + EEPROM_OTHER_MAX_LEN );
247 if ( EEPROM.read( EEPROM_STR_FLAG ) == 1 ){
248 EEPROMStrRead( EEPROM_STR, host );
250 strcpy( host, HOST_DEFAULT ); // default URI and host name
253 WiFi.hostname( host );
255 // WiFi.softAP(APssid, APpassword);
257 // WiFi.mode(WIFI_AP);
258 // WiFi.mode(WIFI_AP_STA);
260 WiFi.begin(ssid, password);
262 if (WiFi.waitForConnectResult() == WL_CONNECTED) {
264 // MDNS.begin( deviceURI.c_str() );
269 server.onNotFound( []() {
270 server.sendHeader("Connection", "close");
272 unsigned int RGBW = 0xff;
276 // parse plain URI arguments TODO: not sure whether we need it, check Plan 9 specifications
278 param = server.arg("rgbw");
280 param = URIHasArg( server.uri(), "rgbw" );
285 if ( server.hasArg("rgbw") ){ // || param != 0
287 param = server.arg("rgbw");
289 Rnew = str2HEX( param.substring(0, 2) );
290 Gnew = str2HEX( param.substring(2, 4) );
291 Bnew = str2HEX( param.substring(4, 6) );
292 Wnew = str2HEX( param.substring(6, 8) );
294 changeRGBto( Rnew, Gnew, Bnew, Wnew );
304 if ( server.hasArg("rotate") ){
305 rotateDelay = str2HEX( server.arg("rotate") );
310 if ( server.hasArg("rename") ) {
311 EEPROMStrWrite( EEPROM_STR, (char *)server.arg("rename").c_str() );
312 EEPROM.write( EEPROM_STR_FLAG, 1 );
315 server.sendHeader("Connection", "close");
316 server.send_P(200, "text/plain", PSTR("Done. System rebooting...\n") );
324 HTTPresp = "host: " + String(host) + ".lan" + "\n";
325 HTTPresp += "URI: " + server.uri() + "\n";
326 // HTTPresp += "Arg RGBW: " + String( URIHasArg( server.uri(), "test2" ) ) + "\n"; // URI contains /test2/
327 // HTTPresp += "Arg RGBW: " + server.arg("aaaa") + "\n"; // GET or POST params has "aaaa"
328 HTTPresp += "rotate: " + String(rotateDelay) + "\n";
329 HTTPresp += "rgbw: " + String(R) + " " + String(G) + " " + String(B) + " " + String(W) + "\n";
331 server.send(200, "text/plain", HTTPresp);
334 // test EEPROM string
336 server.on("/eeprom", HTTP_GET, []() {
338 char * testStr2 = "asdfghjkl12345";
340 server.setContentLength(CONTENT_LENGTH_UNKNOWN);
341 server.send(200, "text/plain", "");
343 EEPROMStrRead( EEPROM_STR, testStr2 );
345 server.sendContent( testStr2 );
346 server.sendContent( "\n" );
351 server.on("/help", HTTP_GET, []() {
353 HTTPresp = "Interplay Medium ESP8266 LED RGB(W) PWM Controller. Version: " + String(VERSION) + "\n";
354 HTTPresp += "Written by Dmitry Shalnov (c) 2017. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>. \n\n";
356 HTTPresp += " rgbw Red Green Blue and White components in hexadecimal form (e.g. ff00ff00)\n";
357 HTTPresp += " rotate Delay of components rotation FX in hexadecimal (e.g. ffff), set 0 to stop\n";
358 HTTPresp += " update Wireless update of firmware (see example below)\n";
359 HTTPresp += " help Send this help\n\n";
361 HTTPresp += "Usage: curl http://" + String(host) + ".lan [--data \"rgbw=<hex RGBW>\"] [--data \"rotate=<hex delay, 0 = stop >\"] \n";
362 HTTPresp += " curl http://" + String(host) + ".lan[/rgbw/<hex RGBW>][/rotate/<hex delay>] \n";
363 HTTPresp += "Examples: curl http://" + String(host) + ".lan/?rgbw=00ff00ff&rotate=ff \n";
364 HTTPresp += " curl -F image=@RGBW_Controller.bin " + String(host) + ".lan/update \n";
366 server.sendHeader("Connection", "close");
367 server.send( 200, "text/plain", HTTPresp );
373 server.on("/update", HTTP_POST, []() {
375 server.sendHeader("Connection", "close");
376 server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
382 HTTPUpload& upload = server.upload();
384 if (upload.status == UPLOAD_FILE_START) {
390 Serial.setDebugOutput(true);
395 Serial.printf("Update: %s\n", upload.filename.c_str());
397 uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
399 if (!Update.begin(maxSketchSpace)) { //start with max available size
401 Update.printError(Serial);
404 } else if (upload.status == UPLOAD_FILE_WRITE) {
405 if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
407 Update.printError(Serial);
410 } else if (upload.status == UPLOAD_FILE_END) {
411 if (Update.end(true)) { //true to set the size to the current progress
413 server.sendHeader("Connection", "close");
414 server.send_P(200, "text/plain", PSTR("Success. Please wait until device replace firmware and boot up...\n") );
416 Serial.printf("Update Success: %u\nRebooting....\n", upload.totalSize);
419 server.sendHeader("Connection", "close");
420 server.send_P(200, "text/plain", PSTR("Something went wrong. Please reset device and try again.\n") );
422 Update.printError(Serial);
426 Serial.setDebugOutput(false);
436 MDNS.addService("http", "tcp", 80);
438 Serial.printf("Ready! Open http://%s.local in your browser\n", host);
443 Serial.println("WiFi Failed");
450 server.handleClient();
455 if ( eTimer >= rotateDelay && rotateDelay != 0 ) {
464 if ( R >= PWMSTEPS-1 ) { signR = -1; R = PWMSTEPS-1; }
465 if ( G >= PWMSTEPS-1 ) { signG = -1; G = PWMSTEPS-1; }
466 if ( B >= PWMSTEPS-1 ) { signB = -1; B = PWMSTEPS-1; }
467 if ( W >= PWMSTEPS-1 ) { signW = -1; W = PWMSTEPS-1; }
469 if ( R < 2 ) { signR = 1; R = 2; }
470 if ( G < 2 ) { signG = 1; G = 2; }
471 if ( B < 2 ) { signB = 1; B = 2; }
472 if ( W < 2 ) { signW = 1; W = 2; }
474 pwm_set_duty( CIEL8[R], 0 );
475 pwm_set_duty( CIEL8[G], 1 );
476 pwm_set_duty( CIEL8[B], 2 );
477 pwm_set_duty( CIEL8[W], 3 );
479 pwm_start(); // commit